package edu.istu.asu07_1.live.model.agents;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.List;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;

import edu.istu.asu07_1.live.configs.Constants;
import edu.istu.asu07_1.live.gui.NotifyPanel;
import edu.istu.asu07_1.live.model.Model;
import edu.istu.asu07_1.live.model.agents.controller.NeuralController;

/**
 * Абстрактный класс для агента, содержит общие методы всех агентов модели
 * @author romanches
 *
 */
public abstract class Agent implements MouseListener{
	protected Random rnd = new Random();

	//Список агентов, участвующих в модели
	protected List<Agent> agentsList;

	//Нейроконтроллер агента
	protected NeuralController controller;

	//Область чувствительности агента
	protected int feelArea;

	//Область действия агента
	protected int nearArea;

	//координаты объекта
	protected int xCoord;
	protected int yCoord;

	//направление движения агента
	protected int moveDirection;

	//Поколение агента
	protected int generation;

	//Колорийность объекта
	protected int colories;

	//Энергия агента, min = 0; max = 100
	protected int energy;

	//Агент должен получать информацию из окружающей среды
	protected Model model;

	//Время жизни агента
	protected int liveTime;

	//Возвращаемый агентом объект, к которому привязываются события
	protected JLabel iconLabel;
	//Изображение агента для каждого направления движения
	//должны быть переопределены в классе-потомке
	protected ImageIcon nordIcon;
	protected ImageIcon westIcon;
	protected ImageIcon southIcon;
	protected ImageIcon eastIcon;
	//Флаг, является ли агент живым
	private boolean isLive;

	/**
	 * Конструктор для нулевого поколения агентов
	 * @param xCoord начальная координата х
	 * @param yCoord начальная координата у
	 * @param moveDirection начальное направление движения
	 * @param energy начальная энергия
	 * @param generation поколение
	 * @param controller нейроконтроллер
	 * @param model класс ссылка на класс Model.java
	 */
	public Agent(int xCoord, int yCoord, int moveDirection, int energy, int generation, NeuralController controller, Model model){
		this.xCoord = xCoord;
		this.yCoord = yCoord;
		this.energy = energy;
		this.generation = generation;
		this.controller = controller;
		this.model = model;
		this.colories = energy/2;
		this.moveDirection = moveDirection;
		this.feelArea = getFeelArea();
		this.nearArea = getNearArea();
		init();
	}

	/**
	 * Конструктор для применения в методе doChild()
	 * @param parent
	 */
	public Agent(Agent parent){
		this.xCoord = parent.getxCoord();
		this.yCoord = parent.getyCoord();
		this.energy = parent.getEnergy();
		this.generation = parent.getGeneration()+1;
		this.controller = parent.getController().getMutateController();
		this.model = parent.getModel();
		this.colories = parent.getEnergy();
		this.moveDirection = rnd.nextInt(3);
		this.feelArea = getFeelArea();
		this.nearArea = getNearArea();
		init();
	}

	private void init(){
		this.isLive = true;
		iconLabel = new JLabel();
		iconLabel.addMouseListener(this);

		model.addAgent(this);
	}

	//Реализует перемещение агента по игровому полю
	public void doStep(){
		iconLabel.setBorder(BorderFactory.createEmptyBorder());
		energy -= getEnergyDecrement();
		liveTime++;
		//Получение информации об кружающей среде
		agentsList = model.getAgentsList();

		int act = getAction();
		switch(act){
		case NeuralController.EAT:
			eat();
			break;
		case NeuralController.TURN_LEFT:
			turnLeft();
			break;
		case NeuralController.TURN_RIGHT:
			turnRight();
			break;
		case NeuralController.MOUVE_FORWARD:
			moveForward();
			break;
		}

		//Секас
		if(energy >= 90){
			energy /= 2;
			Agent child = doChild();
		}
		//Пересчет колорийности агента
		colories = energy/2;
		//		Гибель агента от голода
		if(energy <=0){
			killAgent();
		}
	}
	//Метод реализует гибель агента
	public int killAgent(){
		model.removeAgent(this);
		isLive = false;
		return colories;
	}

	protected void turnLeft(){
		switch(moveDirection){
		case NORD:
			moveDirection = WEST;
			break;
		case WEST:
			moveDirection = SOUTH;
			break;
		case SOUTH:
			moveDirection = EAST;
			break;
		case EAST:
			moveDirection = NORD;
		}
	}

	protected void turnRight(){
		switch(moveDirection){
		case NORD:
			moveDirection = EAST;
			break;
		case WEST:
			moveDirection = NORD;
			break;
		case SOUTH:
			moveDirection = WEST;
			break;
		case EAST:
			moveDirection = SOUTH;
		}
	}

	protected void moveForward(){
		switch(moveDirection){
		case NORD:
			yCoord = verifyYCoord(yCoord-1);
			break;
		case WEST:
			xCoord = verifyXCoord(xCoord-1);
			break;
		case SOUTH:
			yCoord = verifyYCoord(yCoord+1);
			break;
		case EAST:
			xCoord = verifyXCoord(xCoord+1);
		}
	}

	//	самая "тяжелая" функция, при оптимизации нужно разбить на более мелкие куски кода
	private int getAction(){
		int[] inputs = new int[12];
		Agent a;
		switch(moveDirection){
		case NORD:
			//			сканируем площадь справа от агента
			for(int i=(xCoord+nearArea+1); i<=(xCoord+nearArea+feelArea); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord); j++){

					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[2] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[1] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[0] = 1;
							}
						}
					}

				}
			}

			//			сканируем площадь впереди от агента
			for(int i=(xCoord-nearArea-feelArea); i<=(xCoord+nearArea+feelArea); i++){
				for(int j=(yCoord-nearArea-feelArea); j<=(yCoord-nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[5] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[4] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[3] = 1;
							}
						}
					}
				}
			}

			//			сканируем площадь слева от агента
			for(int i=(xCoord-nearArea-feelArea); i<=(xCoord-nearArea-1); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[8] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[7] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[6] = 1;
							}
						}
					}
				}
			}

			//		    сканируем площадь  по-близости от агента
			for(int i=(xCoord-nearArea); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[11] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[10] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[9] = 1;
							}
						}
					}
				}
			}
			break;
		case WEST:
			//			сканируем площадь справа от агента
			for(int i=(xCoord-nearArea); i<=(xCoord); i++){
				for(int j=(yCoord-nearArea-feelArea); j<=(yCoord-feelArea-1); j++){

					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[2] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[1] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[0] = 1;
							}
						}
					}

				}
			}

			//			сканируем площадь впереди от агента
			for(int i=(xCoord-nearArea-feelArea); i<=(xCoord-nearArea-1); i++){
				for(int j=(yCoord-nearArea-feelArea); j<=(yCoord+nearArea+feelArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[5] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[4] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[3] = 1;
							}
						}
					}
				}
			}

			//			сканируем площадь слева от агента
			for(int i=(xCoord-nearArea); i<=(xCoord); i++){
				for(int j=(yCoord+nearArea+1); j<=(yCoord+nearArea+feelArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[8] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[7] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[6] = 1;
							}
						}
					}
				}
			}

			//		    сканируем площадь  по-близости от агента
			for(int i=(xCoord-nearArea); i<=(xCoord); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[11] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[10] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[9] = 1;
							}
						}
					}
				}
			}
			break;
		case SOUTH:
			//			сканируем площадь справа от агента
			for(int i=(xCoord-nearArea-feelArea); i<=(xCoord-nearArea-1); i++){
				for(int j=(yCoord); j<=(yCoord+feelArea); j++){

					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[2] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[1] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[0] = 1;
							}
						}
					}

				}
			}

			//			сканируем площадь впереди от агента
			for(int i=(xCoord-nearArea-feelArea); i<=(xCoord+nearArea+feelArea); i++){
				for(int j=(yCoord+nearArea+1); j<=(yCoord+nearArea+feelArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[5] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[4] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[3] = 1;
							}
						}
					}
				}
			}

			//			сканируем площадь слева от агента
			for(int i=(xCoord+nearArea+1); i<=(xCoord+nearArea+feelArea); i++){
				for(int j=(yCoord); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[8] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[7] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[6] = 1;
							}
						}
					}
				}
			}

			//		    сканируем площадь  по-близости от агента
			for(int i=(xCoord-nearArea); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[11] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[10] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[9] = 1;
							}
						}
					}
				}
			}
			break;
		case EAST:
			//			сканируем площадь справа от агента
			for(int i=(xCoord); i<=(xCoord+feelArea); i++){
				for(int j=(yCoord+nearArea+1); j<=(yCoord+nearArea+feelArea); j++){

					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[2] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[1] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[0] = 1;
							}
						}
					}

				}
			}

			//			сканируем площадь впереди от агента
			for(int i=(xCoord+nearArea+1); i<=(xCoord+nearArea+feelArea); i++){
				for(int j=(yCoord-nearArea-feelArea); j<=(yCoord+nearArea+feelArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[5] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[4] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[3] = 1;
							}
						}
					}
				}
			}

			//			сканируем площадь слева от агента
			for(int i=(xCoord); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord-nearArea-feelArea); j<=(yCoord-nearArea-1); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[8] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[7] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[6] = 1;
							}
						}
					}
				}
			}

			//		    сканируем площадь  по-близости от агента
			for(int i=(xCoord); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(Herb.class)){
								inputs[11] = 1;
							}
							if(a.getClass().equals(Herbivore.class)){
								inputs[10] = 1;
							}
							if(a.getClass().equals(Predator.class)){
								inputs[9] = 1;
							}
						}
					}
				}
			}
		}
		return controller.getAction(inputs);
	}

	//	Методы проверяют соответствие координат. Если агент заходит на край поля
	//	то методы изменяют координаты таким образом, что он перемещается на
	//	противоположную сторону поля
	protected int verifyXCoord(int xCoord){
		if(xCoord <= 0){
			return Constants.FIELD_WIDTH+xCoord;
		}else if(xCoord >= (Constants.FIELD_WIDTH + 1)){
			return xCoord - Constants.FIELD_WIDTH;
		}
		return xCoord;
	}

	protected int verifyYCoord(int yCoord){
		if(yCoord <= 0){
			return Constants.FIELD_HEIGHT+yCoord;
		}else if(yCoord >= (Constants.FIELD_HEIGHT +1)){
			return yCoord - Constants.FIELD_HEIGHT;
		}
		return yCoord;
	}

	protected void eat(){
		Agent a;
		switch(moveDirection){
		case NORD:
			//		    сканируем площадь  по-близости от агента
			loop: for(int i=(xCoord-nearArea); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(getFoodClass())){
								energy+=a.killAgent();
								break loop;
							}
						}
					}
				}
			}
			break;
		case WEST:
			//		    сканируем площадь  по-близости от агента
			loop: for(int i=(xCoord-nearArea); i<=(xCoord); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(getFoodClass())){
								energy+=a.killAgent();
								break loop;
							}
						}
					}
				}
			}
			break;
		case SOUTH:
			//		    сканируем площадь  по-близости от агента
			loop: for(int i=(xCoord-nearArea); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(getFoodClass())){
								energy+=a.killAgent();
								break loop;
							}
						}
					}
				}
			}
			break;
		case EAST:
			//		    сканируем площадь  по-близости от агента
			loop: for(int i=(xCoord); i<=(xCoord+nearArea); i++){
				for(int j=(yCoord-nearArea); j<=(yCoord+nearArea); j++){
					for(int c=0; c<agentsList.size(); c++){
						a = agentsList.get(c);
						if((a.getxCoord()==verifyXCoord(i))&&(a.getyCoord()==verifyYCoord(j))){
							if(a.getClass().equals(getFoodClass())){
								energy+=a.killAgent();
								break loop;
							}
						}
					}
				}
			}
		}
	}

	public JLabel getImageIcon() {
		switch(moveDirection){
			case NORD:
				iconLabel.setIcon(nordIcon);
				return iconLabel;
			case WEST:
				iconLabel.setIcon(westIcon);
				return iconLabel;
			case SOUTH:
				iconLabel.setIcon(southIcon);
				return iconLabel;
			case EAST:
				iconLabel.setIcon(eastIcon);
				return iconLabel;
		}
		return null;
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		if(e.getButton() == MouseEvent.BUTTON1){
			iconLabel.setBorder(BorderFactory.createLineBorder(Color.YELLOW));
			NotifyPanel.agent = this;
		}
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	//Реализует размножение агента
	protected abstract Agent doChild();

	protected abstract int getEnergyDecrement();

	protected abstract int getFeelArea();

	protected abstract int getNearArea();

	protected abstract Class getFoodClass();


	public int getColories() {
		return colories;
	}

	public void setColories(int colories) {
		this.colories = colories;
	}

	public List<Agent> getAgentsList() {
		return agentsList;
	}

	public void setAgentsList(List<Agent> agentsList) {
		this.agentsList = agentsList;
	}

	public int getGeneration() {
		return generation;
	}

	public void setGeneration(int generation) {
		this.generation = generation;
	}

	public int getEnergy() {
		return energy;
	}

	public void setEnergy(int energy) {
		this.energy = energy;
	}

	public NeuralController getController() {
		return controller;
	}

	public void setController(NeuralController controller) {
		this.controller = controller;
	}

	public Model getModel() {
		return model;
	}

	public void setModel(Model model) {
		this.model = model;
	}

	public int getxCoord() {
		return xCoord;
	}

	public void setxCoord(int xCoord) {
		this.xCoord = xCoord;
	}

	public int getyCoord() {
		return yCoord;
	}

	public void setyCoord(int yCoord) {
		this.yCoord = yCoord;
	}

	public int getMoveDirection() {
		return moveDirection;
	}

	public void setMoveDirection(int moveDirection) {
		this.moveDirection = moveDirection;
	}

	public int getLiveTime() {
		return liveTime;
	}

	public void setLiveTime(int liveTime) {
		this.liveTime = liveTime;
	}

	public boolean isLive() {
		return isLive;
	}

	public void setLive(boolean isLive) {
		this.isLive = isLive;
	}

	public static final int NORD = 0;
	public static final int WEST = 1;
	public static final int SOUTH = 2;
	public static final int EAST = 3;
}
